/*
 *  Entity.cpp
 *  IncrementalAlgorithmSerial
 *
 *  Created by Roman Kutlak on 29/03/2010.
 *  Copyright 2010 Roman Kutlak. All rights reserved.
 *
 */
#include <sstream>
#include <functional>

#include "Entity.h"
#include "Ontology.h"
#include "Utilities.h"

using namespace std;
//using namespace boost;

namespace kutlak {
namespace roman {
namespace gre {
    
    
#define ENTITY_DEBUG 0
    
    //--------------------------------------------------------------------------
#pragma mark -
#pragma mark Constructors / Destructor
    
    Entity::Entity(const string& inName, int inId) :
    mHash(0), mId(inId), mName(inName), mType(Ontology::kType), mProperties() 
    {
        CreateHash();
    }
    
    Entity::Entity(const Entity& src) :
    mHash(src.mHash), mId(src.mId), mName(src.mName), mType(src.mType),
    mProperties(src.begin(), src.end()) {}
    
    Entity::Entity(Entity&& src) :
    mHash(src.mHash), mId(src.Id()), 
    mName(std::move(src.mName)), 
    mType(std::move(src.mType)),
    mProperties(std::move(src.mProperties)) {}
    
    Entity::~Entity() noexcept(true) = default;
    
    Entity& Entity::operator=(Entity rhs) 
    {
        swap(*this, rhs);
        return *this;
    }
    
    void swap(Entity& lhs, Entity& rhs) noexcept(true) 
    {
        std::swap(lhs.mHash, rhs.mHash);
        std::swap(lhs.mId, rhs.mId);
        std::swap(lhs.mName, rhs.mName);
        std::swap(lhs.mType, rhs.mType);
        std::swap(lhs.mProperties, rhs.mProperties);
    }
    
    //--------------------------------------------------------------------------
#pragma mark -
#pragma mark boolean operators
    
    
    bool Entity::operator==(const Entity& rhs) const 
    {
        // entities must at least have the same hash
        if (mHash != rhs.mHash)
            return false;
        
        bool result = false;
        result = mId == rhs.mId && mName == rhs.mName && mType == rhs.mType;
        
        if (result) 
        {
            if (mProperties.size() == rhs.Size()) 
            {
                if (mProperties == rhs.mProperties)
                    return true;
            }
        }
        
        return result;
    }
    
    bool Entity::operator!=(const Entity& rhs) const 
    {
        return !(*this == rhs);
    }
    
    //--------------------------------------------------------------------------
#pragma mark -
#pragma mark I/O
    
    ostream& operator<<(ostream& os, const Entity& obj) 
    {
        obj.Serialise(os);
        return os;
    }
    
    istream& operator>>(istream& is, Entity& obj) 
    {
        Entity tmp;
        tmp.Deserialise(is);
        swap(obj, tmp);
        return is;
    }
    
    void Entity::Serialise(ostream& os) const 
    {
        os << "<entity>\n";
        os << mId << '\n';
        os << mName << '\n';
        os << mType;
        
        for (const auto& p : mProperties) 
        {
            if (!(p.Negation() || p.Inverse()))
                os << p;
        }
        
        os << "</entity>\n";
        
    }
    
    void Entity::Deserialise(istream& is) 
    {
        string tmp;
        if (! (is >> tmp) || "<entity>" != tmp) 
        {
            string message = "Expected <entity>.";
            throw DeserialisationException(std::move(message),
                                           std::move(tmp));
        }
        
        if (!(is >> mId >> std::ws)) 
        {
            string message = "Could not read id.";
            throw DeserialisationException(std::move(message));
        }
        
        if (!getline(is, mName)) 
        {
            string message = "Could not read name.";
            throw DeserialisationException(std::move(message));
        }
        
        Property type;
        try 
        {
            is >> type;
            
            Property p;
            while (is >> p)
                mProperties.emplace(std::move(p));
            
        } 
        catch (DeserialisationException& e) 
        {
            if ("</entity>" != e.data()) 
            {
                string message = "Exception while deserialising Entity: "; 
                throw DeserialisationException(message + e.what(), 
                                               std::move(e.data()));
            }
            // set type last so it does not get replaced.
            mType = type;
        }
        CreateHash();
    }
    
    ostream& Entity::ToRdf(ostream& os) const 
    {
        for (const auto& p : mProperties) 
        {
            if (!(p.Negation() || p.Inverse())) 
            {
                os << "<" << mName << "> ";
                p.ToRdf(os);
                os << " .\n";
            }
        }
        
        return os;
    }
    
    string Entity::ToString() const 
    {
        stringstream os;
        os << "Entity (" << mId << ") \"" << mName;
        os << "\" : " << Voc::Instance().String(mType.Value());
        for (const auto& p : mProperties) 
        {
            os << endl << "\t" << p.ToString();
        }
        os << endl;
        return os.str();
    }
    
    //--------------------------------------------------------------------------
    
#pragma mark -
#pragma mark getters
    
    int Entity::Id() const 
    {
        return mId; 
    }
    
    const string& Entity::Name() const 
    {
        return mName;
    }
    
    const Property& Entity::Type() const 
    {
        return mType;
    }
    
    const Entity::ContainerT& Entity::Properties() const 
    {
        return mProperties;
    }
    
    //--------------------------------------------------------------------------
#pragma mark -
#pragma mark setters
    
    void Entity::SetId(int inId) 
    {
        mId = inId;
        CreateHash();
    }
    
    void Entity::SetName(const std::string& inName) 
    {
        mName = inName;
        CreateHash();
    }
    
    void Entity::SetType(const Property& p) 
    {
        AddProperty(p);
        mType = p;
    }
    
    void Entity::ClearProperties() 
    {
        mProperties.clear();
    }
    
    //--------------------------------------------------------------------------
#pragma mark -
#pragma mark methods for properties
    
    size_t Entity::Size() const 
    {
        return mProperties.size();
    }
    
    size_t Entity::Hash() const 
    {
        return mHash;
    }
    
    bool Entity::IsType(const Property& type) const 
    {
        pOntology o = Ontology::Default();
        if (o) 
        {
            bool result = o->Subsumes(type, mType);
            return result;
        }
        
        return HasProperty(type);
    }
    
    void Entity::AddProperty(const Property& p) 
    {
#ifdef ENTITY_DEBUG
        if (ENTITY_DEBUG > 1)
            cerr << "\n\tAddProperty(Property&& " << p << ")\n";
#endif
        
        if (!HasProperty(p)) 
        {
            // decide if this property is a type of the entity
            if (p.Attribute() == Ontology::kType) 
            {
                pOntology o = Ontology::Default();
                if (o) 
                {
                    // no type yet?
                    if (DEFAULT_VALUE == mType.Value())
                    {
                        mType.SetValue(p.Value());
                    }
                    // is current type more general?
                    else if (o->Subsumes(mType, p)) 
                    {
                        // if so, use the new type
                        mType.SetValue(p.Value());
                    }

                } else { // no ontology
                    // set the type to the last non-default value
                    if (p.Value() != DEFAULT_VALUE)
                        mType.SetValue(p.Value());
                }
            }
            
            mProperties.insert(p);
        }
    }
    
    void Entity::AddProperty(Property&& p) 
    {
#ifdef ENTITY_DEBUG
        if (ENTITY_DEBUG > 1)
            cerr << "\n\tAddProperty(Property&& " << p << ")\n";
#endif
        
        if (!HasProperty(p)) 
        {
            // decide if this property is a type of the entity
            if (p.Attribute() == Ontology::kType) 
            {
                pOntology o = Ontology::Default();
                if (o) 
                {
                    // no type yet?
                    if (DEFAULT_VALUE == mType.Value())
                    {
                        mType.SetValue(p.Value());
                    }
                    // is current type more general?
                    else if (o->Subsumes(mType, p)) 
                    {
                        // if so, use the new type
                        mType.SetValue(p.Value());
                    }
                    
                } else { // no ontology
                    // set the type to the last non-default value
                    if (p.Value() != DEFAULT_VALUE)
                        mType.SetValue(p.Value());
                }
            }
            
            mProperties.emplace(std::move(p));
        }
    }
    
    bool Entity::HasProperty(const Property& p) const 
    {
        bool result = false;
        auto it = mProperties.find(p);
        result = (it != mProperties.end());
        
        return result;
    }
    
    bool Entity::HasAttribute(const Vindex_t& attribute) const 
    {
        bool result = false;
        for (const auto& p : mProperties) 
        {
            if (p.Attribute() == attribute) 
            {
                result = true;
            }
        }
        
        return result;
    }
    
    bool Entity::HasValue(const Vindex_t& value) const 
    {
        bool result = false;
        for (const auto& p : mProperties) 
        {
            if (p.Value() == value && !p.Negation()) 
            {
                result = true;
            }
        }
        
        return result;
    }
    
    Vindex_t Entity::ValueForAttribute(const Vindex_t& attribute) const 
    {
        Vindex_t result = DEFAULT_VALUE;
        for (const auto& p : mProperties) 
        {
            if (p.Attribute() == attribute &&
                p.Negation() == false) { // do not add negated attributes 
                // (e.g., <not colour red> should not return "red"
                //  as value for attribute "colour")
                result = p.Value();
            }
        }
        
        return result;
    }
    
    vector<Vindex_t> 
    Entity::ValuesForAttribute(const Vindex_t& attribute) const 
    {
        vector<Vindex_t> result;
        for (const auto& p : mProperties) 
        {
            if (p.Attribute() == attribute &&
                p.Negation() == false) { 
                result.push_back(p.Value());
            }
        }
        
        return result;
    }
    
    void Entity::CreateHash() 
    {
        mHash = std::hash<std::string>()(mName);
    }
    
} // namespace gre
} // namespace roman
} // namespace kutlak
